/*
 * service.c
 *
 *  Created on: Aug 5, 2016
 *      Author: swolice
 */
/*
 * epoll是 Linux下 多路复用IO接口select/poll的 增强版本，它能显著提高程序在大量并发连接
 * 中，只有少量活跃的情况下的 系统 CPU利用率，因为它会复用文件描述符集合来传递结果而不用迫使
 * 开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合，另一点就是获取事件的时候，它
 * 无须遍历整个被侦听的描述符集，只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
 * epoll是目前Linux大规模并发网络程序中的热门首选模型 -- nginx
 * epoll除了提供select/poll那种IO事件的电平触发（level triggered）外，还提供了边沿触发（edge triggered），
 * 这就使得用户空间程序有可能缓存IO状态，减少epoll_wait/epoll_pwait的调用，提高应用程序效率。
 *
 * epoll API
 * 1.创建一个epoll句柄，参数size用来告诉内核监听的文件描述符个数，跟内存大小有关
 * int epoll_create(int size);
 *
 * 2.控制某个epoll监控的 文件描述符上的事件：注册 修改 删除 。
 * #include <sys/epoll.h>
 * int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)
 * 	epfd: 为epoll_create的句柄
 * 	op: 表示动作，用 3个 宏来表示
 * 	EPOLL_CTL_ADD - 注册新的fd到epfd
 * 	EPOLL_CTL_MOD - 修改已经注册的fd的监听事件
 * 	EPOLL_CTL_DEL - 从epfd删除一个fd
 * event： 告诉内核需要监听的事件
 * 	struct epoll_event  {
 * 		__uint32_t events;
 * 		epoll_data_t data;
 * 	};
 *
 * 	EPOLLIN: 表示对应的文件描述符可以读（包括对端SOCKET正常关闭）
 * 	EPOLLOUT: 表示对应的文件描述符可以写
 * 	EPOLLPRI: 表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）
 * 	EPOLLERR: 表示对应的文件描述符发生错误
 * 	EPOLLHUP: 表示对应的文件描述符被挂断
 * 	EPOLLET: 将epoll设为边缘触发（edge triggered）模式，这是相对于水平触发（level triggered）来说的。
 * 	EPOLLONESHOT: 只监听一次事件，当监听完这次事件后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到epoll队列里。
 *
 * 	3.等待所监控文件描述符上有事件的产生，类似于select调用 。
 * 	#include <sys/epoll.h>
 * 	int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
 * 		events: 用来从内核得到事件的集合。
 * 		maxevents：告知内核这个events有多大，这个maxevents的值不能大于创建epollcreate（）时的size，
 * 		timeout：超时时间
 * 			-1：阻塞
 * 			0：立即返回， 非阻塞
 * 			>0:指定微妙
 * 		返回值：成功返回有多少文件描述符就绪，时间到时返回0， 出错返回-1.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000
#define OPEN_MAX 1024

int main(int argc, char* argv[]) {
	int i, j, maxi, listenfd, connfd, sockfd;
	int nready, efd, res;
	ssize_t n;
	char buf[MAXLINE], str[INET_ADDRSTRLEN];
	socklen_t clilen;
	int client[OPEN_MAX];
	struct sockaddr_in cliaddr, servaddr;
	struct epoll_event tep, ep[OPEN_MAX];

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));

	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

	Listen(listenfd, 20);

	for (i = 0; i < OPEN_MAX; i++) {
		client[i] = -1;
	}
	maxi = -1;

	efd = epoll_create(OPEN_MAX);

	if (efd == -1) {
		perr_exit("epoll_create");
	}

	tep.events = EPOLLIN;
	tep.data.fd = listenfd;
	res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);

	if (res == -1) {
		perr_exit("epoll_ctl");
	}

	for (;;) {
		nready = epoll_wait(efd, ep, OPEN_MAX, -1);		/* 阻塞监听*/
		if (nready == -1) {
			perr_exit("epoll_wait");
		}
		for (i = 0; i < nready; i++) {
			if (!(ep[i].events & EPOLLIN)) {
				continue;
			}
			if (ep[i].data.fd == listenfd) {
				clilen = sizeof(cliaddr);
				connfd = Accept(listenfd, (struct sockaddr *) &cliaddr,
						&clilen);
				printf("received from %s at port %d \n",
						inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
						ntohs(cliaddr.sin_port));
				for (j = 0; j < OPEN_MAX; j++) {
					if (client[j] < 0) {
						client[j] = connfd;		/* 保存描述符 */
						break;
					}
				}
				if (j == OPEN_MAX) {
					perr_exit("too many clients");
				}
				if (j > maxi) {
					perr_exit("too many clients");
				}
				if (j > maxi) {
					maxi = j;		/* client[]数组最大索引 */
				}
				tep.events = EPOLLIN;
				tep.data.fd = connfd;
				res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
				if (res == -1) {
					perr_exit("epoll_ctl");
				}
			} else {
				sockfd = ep[i].data.fd;
				n = Read(sockfd, buf, MAXLINE);
				if (n == 0) {
					for (j = 0; j < maxi; j++) {
						if (client[j] == sockfd) {
							client[j] = -1;
							break;
						}
					}
					res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
					if (res == -1) {
						perr_exit("epoll_ctl");
					}
					Close(sockfd);
					printf("client[%d] closed connection\n", j);
				} else {
					for (j = 0; j < n; j++) {
						buf[j] = toupper(buf[j]);
					}
					Write(sockfd, buf, n);
				}
			}
		}
	}

	close(listenfd);
	close(efd);
	return 0;

}

